package com.dc;

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.fastjson.JSON;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据源配置类
 */
@Slf4j
@Configuration
public class DataSourceComponent implements EnvironmentAware {
    private Environment evn;

    /**
     * 别名
     */
    private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
    private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";

    /**
     * 由于部分数据源配置不同，所以在此处添加别名，避免切换数据源出现某些参数无法注入的情况
     */
    static {
        aliases.addAliases("url", new String[]{"url"});
        aliases.addAliases("username", new String[]{"username"});
    }


    @Primary//不加这个会报错。
    @Bean(name = "multiDataSource")
    public MultiRouteDataSource exampleRouteDataSource() {
        List<String> dataSourceIds = MultiRouteDataSource.dataSourceIds;
        MultiRouteDataSource multiDataSource = new MultiRouteDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        // 获取所有数据源配置
        String typeStr = DATASOURCE_TYPE_DEFAULT;
        String url = evn.getProperty("spring.datasource.dynamic.datasource.master.url");
        String name = evn.getProperty("spring.datasource.dynamic.datasource.master.name");
        String username = evn.getProperty("spring.datasource.dynamic.datasource.master.username");
        String password = evn.getProperty("spring.datasource.dynamic.datasource.master.password");
        Map defauleDataSourceProperties = new HashMap();
        defauleDataSourceProperties.put("url", url);
        defauleDataSourceProperties.put("name", name);
        defauleDataSourceProperties.put("username", username);
        defauleDataSourceProperties.put("password", password);

        evn.getProperty("spring.datasource.dynamic.datasource.master");
        Class<? extends DataSource> clazz = getDataSourceType(typeStr);
        // 绑定默认数据源参数 也就是主数据源
        System.out.println("当前数据源" + defauleDataSourceProperties);
        DataSource consumerDatasource, defaultDatasource = bind(clazz, defauleDataSourceProperties);
        targetDataSources.put("master", defaultDatasource);
        dataSourceIds.add("master");
        // 获取其他数据源配置
        Connection conn = null;
        try {
            conn = defaultDatasource.getConnection();
            String sql = "select * from sys_data_source ";
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            ResultSet rs = preparedStatement.executeQuery();
            while (rs.next()) {
                String key = rs.getString("id");
                defauleDataSourceProperties.put("name", key);
                defauleDataSourceProperties.put("url", rs.getString("url"));
                defauleDataSourceProperties.put("username", rs.getString("username"));
                defauleDataSourceProperties.put("password", rs.getString("password"));
                // 绑定参数
                consumerDatasource = bind(clazz, defauleDataSourceProperties);
                targetDataSources.put(key, consumerDatasource);
                dataSourceIds.add(key);
            }
            rs.close();
            preparedStatement.close();
            conn.close();
        } catch (SQLException e) {
            log.error(e.getMessage());
        }
        // 将该bean注册为datasource，不使用springboot自动生成的datasource
        log.info("注册数据源成功，一共注册{}个数据源", targetDataSources.keySet().size());
        multiDataSource.setTargetDataSources(targetDataSources);
        multiDataSource.setDefaultTargetDataSource(defaultDatasource);
        log.info("数据源为:" + JSON.toJSONString(dataSourceIds));
        return multiDataSource;
    }


    /**
     * 注册ServletRegistrationBean
     *
     * @return
     */
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("allow", ""); // 白名单 return reg;
        reg.addInitParameter("loginUsername", "admin");
        reg.addInitParameter("loginPassword", "admin");
        reg.addInitParameter("resetEnable", "false");
        return reg;
    }

    /**
     * 事务管理器
     *
     * @return the platform transaction manager
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(exampleRouteDataSource());
    }

    /**
     * 注册FilterRegistrationBean
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
        filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
        filterRegistrationBean.addInitParameter("DruidWebStatFilter", "/*");
        return filterRegistrationBean;
    }

    /**
     * 通过字符串获取数据源class对象
     *
     * @param typeStr
     * @return
     */
    public Class<? extends DataSource> getDataSourceType(String typeStr) {
        Class<? extends DataSource> type;
        try {
            if (StringUtils.hasLength(typeStr)) {
                // 字符串不为空则通过反射获取class对象
                type = (Class<? extends DataSource>) Class.forName(typeStr);
            } else {
                // 默认为hikariCP数据源，与springboot默认数据源保持一致
                type = HikariDataSource.class;
            }
            return type;
        } catch (Exception e) {
            throw new IllegalArgumentException("can not resolve class with type: " + typeStr); //无法通过反射获取class对象的情况则抛出异常，该情况一般是写错了，所以此次抛出一个runtimeexception
        }
    }

    public <T extends DataSource> T bind(Class<T> clazz, Map properties) {
        ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
        Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
        // 通过类型绑定参数并获得实例对象
        return binder.bind(ConfigurationPropertyName.EMPTY, Bindable.of(clazz)).get();
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.evn = environment;
    }
}